const underTest = require('../src/commands/add-s3-event-source'),
	destroyObjects = require('./util/destroy-objects'),
	create = require('../src/commands/create'),
	update = require('../src/commands/update'),
	fsUtil = require('../src/util/fs-util'),
	tmppath = require('../src/util/tmppath'),
	fs = require('fs'),
	path = require('path'),
	retry = require('oh-no-i-insist'),
	aws = require('aws-sdk'),
	genericRole = require('./util/generic-role'),
	awsRegion = require('./util/test-aws-region');
describe('addS3EventSource', () => {
	'use strict';
	const s3 = new aws.S3({region: awsRegion});
	describe('validation', () => {
		let workingdir;
		beforeEach(() => {
			workingdir = tmppath();
			fs.mkdirSync(workingdir);
		});
		afterEach(() => {
			fsUtil.silentRemove(workingdir);
		});

		it('fails when the bucket is not defined in options', done => {
			underTest({ source: workingdir })
				.then(done.fail, reason => {
					expect(reason).toEqual('bucket name not specified. please provide it with --bucket');
					done();
				});
		});
		it('fails when the source dir does not contain the project config file', done => {
			underTest({ bucket: 'b', source: workingdir })
				.then(done.fail, reason => {
					expect(reason).toEqual('claudia.json does not exist in the source folder');
					done();
				});
		});
		it('fails when the project config file does not contain the lambda name', done => {
			fs.writeFileSync(path.join(workingdir, 'claudia.json'), '{}', 'utf8');
			underTest({ bucket: 'b', source: workingdir })
				.then(done.fail, reason => {
					expect(reason).toEqual('invalid configuration -- lambda.name missing from claudia.json');
					done();
				});
		});
		it('fails when the project config file does not contain the lambda region', done => {
			fs.writeFileSync(path.join(workingdir, 'claudia.json'), JSON.stringify({ lambda: { name: 'xxx' } }), 'utf8');
			underTest({ bucket: 'b', source: workingdir })
				.then(done.fail, reason => {
					expect(reason).toEqual('invalid configuration -- lambda.region missing from claudia.json');
					done();
				});
		});
		it('fails when the project config file does not contain the lambda role', done => {
			fs.writeFileSync(path.join(workingdir, 'claudia.json'), JSON.stringify({ lambda: { name: 'xxx', region: 'abc' } }), 'utf8');
			underTest({ bucket: 'b', source: workingdir })
				.then(done.fail, reason => {
					expect(reason).toEqual('invalid configuration -- lambda.role missing from claudia.json');
					done();
				});
		});

	});

	describe('using an autogenerated role', () => {
		let genericFunction,
			newObjects,
			workingdir,
			bucketName,
			testRunName;
		const getBucketNotifications = function (expectedConfigs) {
			const length = expectedConfigs || 1;
			return retry(() => {
				return s3.getBucketNotificationConfiguration({
					Bucket: bucketName
				}).promise()
					.then(config => {
						if (config && config.LambdaFunctionConfigurations && config.LambdaFunctionConfigurations.length >= length) {
							return config;
						} else {
							return Promise.reject();
						}
					});
			}, 2000, 5);
		};

		beforeAll(done => {
			workingdir = tmppath();
			fs.mkdirSync(workingdir);
			genericFunction = {workingdir: workingdir};
			testRunName = 'test' + Date.now();
			fsUtil.copy('spec/test-projects/s3-remover', workingdir, true);
			create({ name: testRunName, region: awsRegion, source: workingdir, handler: 'main.handler', 'optional-dependencies': false, version: 'special' })
			.then(result => {
				genericFunction.lambdaRole = result.lambda && result.lambda.role;
				genericFunction.lambdaFunction = result.lambda && result.lambda.name;
			})
			.then(done, done.fail);
		});
		afterAll(done => {
			destroyObjects(genericFunction)
			.then(done, done.fail);
		});
		afterEach(done => {
			destroyObjects(newObjects)
				.then(done, done.fail);
		});
		beforeEach(done => {
			newObjects = { };
			bucketName = 'test' + Date.now() + '.bucket';
			s3.createBucket({
				Bucket: bucketName,
				ACL: 'private'
			}).promise()
			.then(() => {
				newObjects.s3Bucket = bucketName;
			})
			.then(done);
		});
		it('sets up privileges and s3 notifications for any created files in the s3 bucket', done => {
			underTest({source: workingdir, bucket: bucketName})
			.then(() => getBucketNotifications())
			.then(() => {
				return s3.putObject({
					Bucket: bucketName,
					Key: `${testRunName}.txt`,
					Body: 'file contents',
					ACL: 'private'
				}).promise();
			})
			.then(() => {
				return s3.waitFor('objectNotExists', {
					Bucket: bucketName,
					Key: `${testRunName}.txt`
				}).promise();
			})
			.then(done, done.fail);
		});
		it('adds a prefix if requested', done => {
			underTest({source: workingdir, bucket: bucketName, prefix: 'in/'})
			.then(() => getBucketNotifications())
			.then(config => {
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Prefix',
					Value: 'in/'
				});
			})
			.then(done, done.fail);
		});
		it('adds a suffix if requested', done => {
			underTest({source: workingdir, bucket: bucketName, suffix: '.jpg'})
			.then(() => getBucketNotifications())
			.then(config => {
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Suffix',
					Value: '.jpg'
				});
			})
			.then(done, done.fail);
		});
		it('adds both a prefix and suffix if requested', done => {
			underTest({source: workingdir, bucket: bucketName, prefix: 'in/', suffix: '.jpg'})
			.then(() => getBucketNotifications())
			.then(config => {
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Prefix',
					Value: 'in/'
				});
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[1]).toEqual({
					Name: 'Suffix',
					Value: '.jpg'
				});
			})
			.then(done, done.fail);
		});
		it('adds default event if no events requested', done => {
			underTest({ source: workingdir, bucket: bucketName })
			.then(() => getBucketNotifications())
			.then(config => expect(config.LambdaFunctionConfigurations[0].Events).toEqual(['s3:ObjectCreated:*']))
			.then(done, done.fail);
		});
		it('adds events if requested', done => {
			underTest({ source: workingdir, bucket: bucketName, events: 's3:ObjectCreated:*,s3:ObjectRemoved:*' })
			.then(() => getBucketNotifications())
			.then(config => expect(config.LambdaFunctionConfigurations[0].Events.sort()).toEqual(['s3:ObjectCreated:*', 's3:ObjectRemoved:*']))
			.then(done, done.fail);
		});
		it('allows adding several events for the same bucket', done => {
			underTest({ source: workingdir, bucket: bucketName, events: 's3:ObjectCreated:*', prefix: '/in/' })
			.then(() => underTest({ source: workingdir, bucket: bucketName, events: 's3:ObjectRemoved:*', prefix: '/out/'}))
			.then(() => getBucketNotifications(2))
			.then(config => {
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Prefix',
					Value: '/in/'
				});
				expect(config.LambdaFunctionConfigurations[1].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Prefix',
					Value: '/out/'
				});
			})
			.then(done, done.fail);
		});
		it('binds to an alias, if the version is provided', done => {
			underTest({ source: workingdir, bucket: bucketName, version: 'special' })
			.then(() => getBucketNotifications())
			.then(config => expect(/:special$/.test(config.LambdaFunctionConfigurations[0].LambdaFunctionArn)).toBeTruthy())
			.then(done, done.fail);
		});
		it('can execute aliased functions', done => {
			underTest({ source: workingdir, bucket: bucketName, version: 'special' })
			.then(() => getBucketNotifications())
			.then(() => {
				return s3.putObject({
					Bucket: bucketName,
					Key: `${testRunName}.txt`,
					Body: 'file contents',
					ACL: 'private'
				}).promise();
			})
			.then(() => {
				return s3.waitFor('objectNotExists', {
					Bucket: bucketName,
					Key: `${testRunName}.txt`
				}).promise();
			})
			.then(done, done.fail);
		});
		it('does not change any existing notification configurations', done => {
			underTest({ source: workingdir, bucket: bucketName, version: 'special',  prefix: '/special/' })
			.then(() => update({ source: workingdir, version: 'crazy' }))
			.then(() => underTest({ source: workingdir, bucket: bucketName, version: 'crazy',  prefix: '/crazy/' }))
			.then(() => getBucketNotifications())
			.then(config => {
				expect(config.LambdaFunctionConfigurations.length).toEqual(2);
				expect(config.LambdaFunctionConfigurations[0].LambdaFunctionArn).toMatch(/:special$/);
				expect(config.LambdaFunctionConfigurations[1].LambdaFunctionArn).toMatch(/:crazy$/);
			})
			.then(done, done.fail);
		});
		it('can use functions created using a role ARN', done => {
			const anotherdir = path.join(workingdir, 'subdir');
			newObjects.workingdir = anotherdir;
			fs.mkdirSync(anotherdir);
			fsUtil.copy('spec/test-projects/s3-remover', anotherdir, true);
			genericRole.create(testRunName + '-exec').then(roleResult => {
				newObjects.lambdaRole = roleResult.Role.RoleName;
				return roleResult.Role.Arn;
			}).then(roleArn => create({
				role: roleArn,
				name: testRunName + '-x',
				region: awsRegion,
				source: anotherdir,
				handler: 'main.handler'
			})).then(result => {
				newObjects.lambdaFunction = result.lambda && result.lambda.name;
			})
			.then(() => underTest({source: anotherdir, bucket: bucketName, prefix: 'in/'}))
			.then(() => getBucketNotifications())
			.then(config => {
				expect(config.LambdaFunctionConfigurations[0].Filter.Key.FilterRules[0]).toEqual({
					Name: 'Prefix',
					Value: 'in/'
				});
			})
			.then(done, done.fail);
		});
	});
});
